/*
 * Copyright 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.compose.ui.text

import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.drawscope.Fill
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.lerp
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontSynthesis
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.font.lerp
import androidx.compose.ui.text.intl.LocaleList
import androidx.compose.ui.text.style.BaselineShift
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.style.TextGeometricTransform
import androidx.compose.ui.text.style.lerp
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.em
import androidx.compose.ui.unit.isUnspecified
import androidx.compose.ui.unit.sp
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4

@RunWith(JUnit4::class)
class SpanStyleTest {
    @Test
    fun `constructor with default values`() {
        val style = SpanStyle()

        assertThat(style.brush).isNull()
        assertThat(style.color).isEqualTo(Color.Unspecified)
        assertThat(style.fontSize.isUnspecified).isTrue()
        assertThat(style.fontWeight).isNull()
        assertThat(style.fontStyle).isNull()
        assertThat(style.letterSpacing.isUnspecified).isTrue()
        assertThat(style.localeList).isNull()
        assertThat(style.background).isEqualTo(Color.Unspecified)
        assertThat(style.textDecoration).isNull()
        assertThat(style.fontFamily).isNull()
        assertThat(style.drawStyle).isNull()
    }

    @Test
    fun `constructor with customized brush`() {
        val brush = Brush.linearGradient(colors = listOf(Color.Blue, Color.Red))

        val style = SpanStyle(brush = brush)

        assertThat(style.brush).isEqualTo(brush)
    }

    @Test
    fun `constructor with customized brush and alpha`() {
        val brush = Brush.linearGradient(colors = listOf(Color.Blue, Color.Red))

        val style = SpanStyle(brush = brush, alpha = 0.3f)

        assertThat(style.brush).isEqualTo(brush)
        assertThat(style.alpha).isEqualTo(0.3f)
    }

    @Test
    fun `constructor with gradient brush has unspecified color`() {
        val brush = Brush.linearGradient(colors = listOf(Color.Blue, Color.Red))

        val style = SpanStyle(brush = brush)

        assertThat(style.color).isEqualTo(Color.Unspecified)
    }

    @Test
    fun `constructor with SolidColor converts to regular color`() {
        val brush = SolidColor(Color.Red)

        val style = SpanStyle(brush = brush)

        assertThat(style.color).isEqualTo(Color.Red)
    }

    @Test
    fun `constructor with customized color`() {
        val color = Color.Red

        val style = SpanStyle(color = color)

        assertThat(style.color).isEqualTo(color)
    }

    @Test
    fun `constructor with half-transparent color`() {
        val color = Color.Red.copy(alpha = 0.5f)

        val style = SpanStyle(color = color)

        assertThat(style.color).isEqualTo(color)
        assertThat(style.alpha).isWithin(1f / 256f).of(0.5f)
    }

    @Test
    fun `constructor with customized fontSize`() {
        val fontSize = 18.sp

        val style = SpanStyle(fontSize = fontSize)

        assertThat(style.fontSize).isEqualTo(fontSize)
    }

    @Test
    fun `constructor with customized fontWeight`() {
        val fontWeight = FontWeight.W500

        val style = SpanStyle(fontWeight = fontWeight)

        assertThat(style.fontWeight).isEqualTo(fontWeight)
    }

    @Test
    fun `constructor with customized fontStyle`() {
        val fontStyle = FontStyle.Italic

        val style = SpanStyle(fontStyle = fontStyle)

        assertThat(style.fontStyle).isEqualTo(fontStyle)
    }

    @Test
    fun `constructor with customized letterSpacing`() {
        val letterSpacing = 1.em

        val style = SpanStyle(letterSpacing = letterSpacing)

        assertThat(style.letterSpacing).isEqualTo(letterSpacing)
    }

    @Test
    fun `constructor with customized baselineShift`() {
        val baselineShift = BaselineShift.Superscript

        val style = SpanStyle(baselineShift = baselineShift)

        assertThat(style.baselineShift).isEqualTo(baselineShift)
    }

    @Test
    fun `constructor with customized locale`() {
        val localeList = LocaleList("en-US")

        val style = SpanStyle(localeList = localeList)

        assertThat(style.localeList).isEqualTo(localeList)
    }

    @Test
    fun `constructor with customized background`() {
        val color = Color.Red

        val style = SpanStyle(background = color)

        assertThat(style.background).isEqualTo(color)
    }

    @Test
    fun `constructor with customized textDecoration`() {
        val decoration = TextDecoration.Underline

        val style = SpanStyle(textDecoration = decoration)

        assertThat(style.textDecoration).isEqualTo(decoration)
    }

    @Test
    fun `constructor with customized fontFamily`() {
        val fontFamily = FontFamily.SansSerif

        val style = SpanStyle(fontFamily = fontFamily)

        assertThat(style.fontFamily).isEqualTo(fontFamily)
    }

    @Test
    fun `constructor with customized drawStyle`() {
        val stroke = Stroke(width = 4f)

        val style = SpanStyle(drawStyle = stroke)

        assertThat(style.drawStyle).isEqualTo(stroke)
    }

    @Test
    fun `merge with empty other should return this`() {
        val style = SpanStyle()

        val newSpanStyle = style.merge()

        assertThat(newSpanStyle).isEqualTo(style)
    }

    @Test
    fun `merge with other's color is null should use this color`() {
        val style = SpanStyle(color = Color.Red)

        val newSpanStyle = style.merge(SpanStyle(color = Color.Unspecified))

        assertThat(newSpanStyle.color).isEqualTo(style.color)
    }

    @Test
    fun `merge with other's color is set should use other's color`() {
        val style = SpanStyle(color = Color.Red)
        val otherStyle = SpanStyle(color = Color.Green)

        val newSpanStyle = style.merge(otherStyle)

        assertThat(newSpanStyle.color).isEqualTo(otherStyle.color)
    }

    @Test
    fun `merge with other's fontFamily is unspecified should use this' fontFamily`() {
        val style = SpanStyle(fontFamily = FontFamily.SansSerif)

        val newSpanStyle = style.merge(SpanStyle(fontFamily = null))

        assertThat(newSpanStyle.fontFamily).isEqualTo(style.fontFamily)
    }

    @Test
    fun `merge with other's fontFamily is set should use other's fontFamily`() {
        val style = SpanStyle(fontFamily = FontFamily.SansSerif)
        val otherStyle = SpanStyle(fontFamily = FontFamily.Serif)

        val newSpanStyle = style.merge(otherStyle)

        assertThat(newSpanStyle.fontFamily).isEqualTo(otherStyle.fontFamily)
    }

    @Test
    fun `merge with other's fontSize is null should use this' fontSize`() {
        val style = SpanStyle(fontSize = 3.5.sp)

        val newSpanStyle = style.merge(SpanStyle(fontSize = TextUnit.Unspecified))

        assertThat(newSpanStyle.fontSize).isEqualTo(style.fontSize)
    }

    @Test
    fun `merge with other's fontSize is set should use other's fontSize`() {
        val style = SpanStyle(fontSize = 3.5.sp)
        val otherStyle = SpanStyle(fontSize = 8.7.sp)

        val newSpanStyle = style.merge(otherStyle)

        assertThat(newSpanStyle.fontSize).isEqualTo(otherStyle.fontSize)
    }

    @Test
    fun `merge with other's fontWeight is null should use this' fontWeight`() {
        val style = SpanStyle(fontWeight = FontWeight.W300)

        val newSpanStyle = style.merge(SpanStyle(fontWeight = null))

        assertThat(newSpanStyle.fontWeight).isEqualTo(style.fontWeight)
    }

    @Test
    fun `merge with other's fontWeight is set should use other's fontWeight`() {
        val style = SpanStyle(fontWeight = FontWeight.W300)
        val otherStyle = SpanStyle(fontWeight = FontWeight.W500)

        val newSpanStyle = style.merge(otherStyle)

        assertThat(newSpanStyle.fontWeight).isEqualTo(otherStyle.fontWeight)
    }

    @Test
    fun `merge with other's fontStyle is null should use this' fontStyle`() {
        val style = SpanStyle(fontStyle = FontStyle.Italic)

        val newSpanStyle = style.merge(SpanStyle(fontStyle = null))

        assertThat(newSpanStyle.fontStyle).isEqualTo(style.fontStyle)
    }

    @Test
    fun `merge with other's fontStyle is set should use other's fontStyle`() {
        val style = SpanStyle(fontStyle = FontStyle.Normal)
        val otherStyle = SpanStyle(fontStyle = FontStyle.Italic)

        val newSpanStyle = style.merge(otherStyle)

        assertThat(newSpanStyle.fontStyle).isEqualTo(otherStyle.fontStyle)
    }

    @Test
    fun `merge with other's fontSynthesis is null should use this' fontSynthesis`() {
        val style = SpanStyle(fontSynthesis = FontSynthesis.Style)

        val newSpanStyle = style.merge(SpanStyle(fontSynthesis = null))

        assertThat(newSpanStyle.fontSynthesis).isEqualTo(style.fontSynthesis)
    }

    @Test
    fun `merge with other's fontSynthesis is set should use other's fontSynthesis`() {
        val style = SpanStyle(fontSynthesis = FontSynthesis.Style)
        val otherStyle = SpanStyle(fontSynthesis = FontSynthesis.Weight)

        val newSpanStyle = style.merge(otherStyle)

        assertThat(newSpanStyle.fontSynthesis).isEqualTo(otherStyle.fontSynthesis)
    }

    @Test
    fun `merge with other's fontFeature is null should use this' fontSynthesis`() {
        val style = SpanStyle(fontFeatureSettings = "\"kern\" 0")

        val newSpanStyle = style.merge(SpanStyle(fontFeatureSettings = null))

        assertThat(newSpanStyle.fontFeatureSettings).isEqualTo(style.fontFeatureSettings)
    }

    @Test
    fun `merge with other's fontFeature is set should use other's fontSynthesis`() {
        val style = SpanStyle(fontFeatureSettings = "\"kern\" 0")
        val otherStyle = SpanStyle(fontFeatureSettings = "\"kern\" 1")

        val newSpanStyle = style.merge(otherStyle)

        assertThat(newSpanStyle.fontFeatureSettings).isEqualTo(otherStyle.fontFeatureSettings)
    }

    @Test
    fun `merge with other's letterSpacing is unspecified should use this' letterSpacing`() {
        val style = SpanStyle(letterSpacing = 1.2.em)

        val newSpanStyle = style.merge(SpanStyle(letterSpacing = TextUnit.Unspecified))

        assertThat(newSpanStyle.letterSpacing).isEqualTo(style.letterSpacing)
    }

    @Test
    fun `merge with other's letterSpacing is set should use other's letterSpacing`() {
        val style = SpanStyle(letterSpacing = 1.2.em)
        val otherStyle = SpanStyle(letterSpacing = 1.5.em)

        val newSpanStyle = style.merge(otherStyle)

        assertThat(newSpanStyle.letterSpacing).isEqualTo(otherStyle.letterSpacing)
    }

    @Test
    fun `merge with other's baselineShift is null should use this' baselineShift`() {
        val style = SpanStyle(baselineShift = BaselineShift.Superscript)

        val newSpanStyle = style.merge(SpanStyle(baselineShift = null))

        assertThat(newSpanStyle.baselineShift).isEqualTo(style.baselineShift)
    }

    @Test
    fun `merge with other's baselineShift is set should use other's baselineShift`() {
        val style = SpanStyle(baselineShift = BaselineShift.Superscript)
        val otherStyle = SpanStyle(baselineShift = BaselineShift.Subscript)

        val newSpanStyle = style.merge(otherStyle)

        assertThat(newSpanStyle.baselineShift).isEqualTo(otherStyle.baselineShift)
    }

    @Test
    fun `merge with other's background is null should use this' background`() {
        val style = SpanStyle(background = Color.Red)

        val newSpanStyle = style.merge(SpanStyle(background = Color.Unspecified))

        assertThat(newSpanStyle.background).isEqualTo(style.background)
    }

    @Test
    fun `merge with other's background is set should use other's background`() {
        val style = SpanStyle(background = Color.Red)
        val otherStyle = SpanStyle(background = Color.Green)

        val newSpanStyle = style.merge(otherStyle)

        assertThat(newSpanStyle.background).isEqualTo(otherStyle.background)
    }

    @Test
    fun `merge with other's textDecoration is null should use this' textDecoration`() {
        val style = SpanStyle(textDecoration = TextDecoration.LineThrough)

        val newSpanStyle = style.merge(SpanStyle(textDecoration = null))

        assertThat(newSpanStyle.textDecoration).isEqualTo(style.textDecoration)
    }

    @Test
    fun `merge with other's textDecoration is set should use other's textDecoration`() {
        val style = SpanStyle(textDecoration = TextDecoration.LineThrough)
        val otherStyle = SpanStyle(textDecoration = TextDecoration.Underline)

        val newSpanStyle = style.merge(otherStyle)

        assertThat(newSpanStyle.textDecoration).isEqualTo(otherStyle.textDecoration)
    }

    @Test
    fun `merge with other's locale is null should use this' locale`() {
        val style = SpanStyle(localeList = LocaleList("en-US"))

        val newSpanStyle = style.merge(SpanStyle(localeList = null))

        assertThat(newSpanStyle.localeList).isEqualTo(style.localeList)
    }

    @Test
    fun `merge with other's locale is set should use other's locale`() {
        val style = SpanStyle(localeList = LocaleList("en-US"))
        val otherStyle = SpanStyle(localeList = LocaleList("ja-JP"))

        val newSpanStyle = style.merge(otherStyle)

        assertThat(newSpanStyle.localeList).isEqualTo(otherStyle.localeList)
    }

    @Test
    fun `merge with null platformStyles has null platformStyle`() {
        val style = SpanStyle(platformStyle = null)
        val otherStyle = SpanStyle(platformStyle = null)

        val mergedStyle = style.merge(otherStyle)

        assertThat(mergedStyle.platformStyle).isNull()
    }

    @Test
    fun `merge with brush has other brush and no color`() {
        val brush = Brush.linearGradient(listOf(Color.Blue, Color.Red))

        val style = SpanStyle(color = Color.Red)
        val otherStyle = SpanStyle(brush = brush)

        val mergedStyle = style.merge(otherStyle)

        assertThat(mergedStyle.color).isEqualTo(Color.Unspecified)
        assertThat(mergedStyle.brush).isEqualTo(brush)
    }

    @Test
    fun `merge with unspecified brush has original brush`() {
        val brush = Brush.linearGradient(listOf(Color.Blue, Color.Red))

        val style = SpanStyle(brush = brush)
        val otherStyle = SpanStyle()

        val mergedStyle = style.merge(otherStyle)

        assertThat(mergedStyle.color).isEqualTo(Color.Unspecified)
        assertThat(mergedStyle.brush).isEqualTo(brush)
    }

    @Test
    fun `merge brush with brush uses other's alpha`() {
        val brush = Brush.linearGradient(listOf(Color.Blue, Color.Red))

        val style = SpanStyle(brush = brush, alpha = 0.3f)
        val otherStyle = SpanStyle(brush = brush, alpha = 0.6f)

        val mergedStyle = style.merge(otherStyle)

        assertThat(mergedStyle.color).isEqualTo(Color.Unspecified)
        assertThat(mergedStyle.brush).isEqualTo(brush)
        assertThat(mergedStyle.alpha).isEqualTo(0.6f)
    }

    @Test
    fun `merge brush with brush uses current alpha if other's is NaN`() {
        val brush = Brush.linearGradient(listOf(Color.Blue, Color.Red))

        val style = SpanStyle(brush = brush, alpha = 0.3f)
        val otherStyle = SpanStyle(brush = brush)

        val mergedStyle = style.merge(otherStyle)

        assertThat(mergedStyle.color).isEqualTo(Color.Unspecified)
        assertThat(mergedStyle.brush).isEqualTo(brush)
        assertThat(mergedStyle.alpha).isEqualTo(0.3f)
    }

    @Test
    fun `merge with other's drawStyle is null should use this' drawStyle`() {
        val drawStyle1 = Stroke(cap = StrokeCap.Butt)
        val style = SpanStyle(drawStyle = drawStyle1)

        val newSpanStyle = style.merge(SpanStyle(drawStyle = null))

        assertThat(newSpanStyle.drawStyle).isEqualTo(drawStyle1)
    }

    @Test
    fun `merge with other's drawStyle is set should use other's drawStyle`() {
        val drawStyle1 = Stroke(cap = StrokeCap.Butt)
        val drawStyle2 = Fill
        val style = SpanStyle(drawStyle = drawStyle1)
        val otherStyle = SpanStyle(drawStyle = drawStyle2)

        val newSpanStyle = style.merge(otherStyle)

        assertThat(newSpanStyle.drawStyle).isEqualTo(otherStyle.drawStyle)
    }

    @Test
    fun `plus operator merges`() {
        val style = SpanStyle(
            color = Color.Red,
            fontWeight = FontWeight.Bold
        ) + SpanStyle(
            color = Color.Green,
            fontFamily = FontFamily.Cursive
        )

        assertThat(style).isEqualTo(
            SpanStyle(
                color = Color.Green, // overridden by RHS
                fontWeight = FontWeight.Bold, // from LHS,
                fontFamily = FontFamily.Cursive // from RHS
            )
        )
    }

    @Test
    fun `lerp color with a and b are not Null`() {
        val color1 = Color.Red
        val color2 = Color.Green
        val t = 0.3f
        val style1 = SpanStyle(color = color1)
        val style2 = SpanStyle(color = color2)

        val newSpanStyle = lerp(start = style1, stop = style2, fraction = t)

        assertThat(newSpanStyle.color).isEqualTo(lerp(start = color1, stop = color2, fraction = t))
    }

    @Test
    fun `lerp color with a and b are unspecified`() {
        val style1 = SpanStyle(color = Color.Unspecified)
        val style2 = SpanStyle(color = Color.Unspecified)

        val newSpanStyle = lerp(start = style1, stop = style2, fraction = 0.3f)

        assertThat(newSpanStyle.color).isEqualTo(Color.Unspecified)
    }

    @Test
    fun `when lerp from Specified to Unspecified color, uses Color lerp logic`() {
        val t = 0.3f
        val color1 = Color.Red
        val color2 = Color.Unspecified
        val style1 = SpanStyle(color = color1)
        val style2 = SpanStyle(color = color2)

        val newSpanStyle = lerp(start = style1, stop = style2, fraction = t)

        assertThat(newSpanStyle.color).isEqualTo(lerp(start = color1, stop = color2, fraction = t))
    }

    @Test
    fun `lerp fontFamily with a and b are not Null and t is smaller than half`() {
        val fontFamily1 = FontFamily.SansSerif
        val fontFamily2 = FontFamily.Serif
        val t = 0.3f
        val style1 = SpanStyle(fontFamily = fontFamily1)
        val style2 = SpanStyle(fontFamily = fontFamily2)

        val newSpanStyle = lerp(start = style1, stop = style2, fraction = t)

        assertThat(newSpanStyle.fontFamily).isEqualTo(fontFamily1)
    }

    @Test
    fun `lerp fontFamily with a and b are not Null and t is larger than half`() {
        val fontFamily1 = FontFamily.SansSerif
        val fontFamily2 = FontFamily.Serif
        val t = 0.8f
        val style1 = SpanStyle(fontFamily = fontFamily1)
        val style2 = SpanStyle(fontFamily = fontFamily2)

        val newSpanStyle = lerp(start = style1, stop = style2, fraction = t)

        assertThat(newSpanStyle.fontFamily).isEqualTo(fontFamily2)
    }

    @Test
    fun `lerp fontSize with a and b are not Null`() {
        val fontSize1 = 8.sp
        val fontSize2 = 16.sp
        val t = 0.8f
        val style1 = SpanStyle(fontSize = fontSize1)
        val style2 = SpanStyle(fontSize = fontSize2)

        val newSpanStyle = lerp(start = style1, stop = style2, fraction = t)

        // a + (b - a) * t = 8.0f + (16.0f  - 8.0f) * 0.8f = 14.4f
        assertThat(newSpanStyle.fontSize).isEqualTo(14.4.sp)
    }

    @Test
    fun `lerp fontWeight with a and b are not Null`() {
        val fontWeight1 = FontWeight.W200
        val fontWeight2 = FontWeight.W500
        val t = 0.8f
        val style1 = SpanStyle(fontWeight = fontWeight1)
        val style2 = SpanStyle(fontWeight = fontWeight2)

        val newSpanStyle = lerp(start = style1, stop = style2, fraction = t)

        assertThat(newSpanStyle.fontWeight).isEqualTo(lerp(fontWeight1, fontWeight2, t))
    }

    @Test
    fun `lerp fontStyle with a and b are not Null and t is smaller than half`() {
        val fontStyle1 = FontStyle.Italic
        val fontStyle2 = FontStyle.Normal
        // attributes other than fontStyle are required for lerp not to throw an exception
        val t = 0.3f
        val style1 = SpanStyle(fontStyle = fontStyle1)
        val style2 = SpanStyle(fontStyle = fontStyle2)

        val newSpanStyle = lerp(start = style1, stop = style2, fraction = t)

        assertThat(newSpanStyle.fontStyle).isEqualTo(fontStyle1)
    }

    @Test
    fun `lerp fontStyle with a and b are not Null and t is larger than half`() {
        val fontStyle1 = FontStyle.Italic
        val fontStyle2 = FontStyle.Normal
        // attributes other than fontStyle are required for lerp not to throw an exception
        val t = 0.8f
        val style1 = SpanStyle(fontStyle = fontStyle1)
        val style2 = SpanStyle(fontStyle = fontStyle2)

        val newSpanStyle = lerp(start = style1, stop = style2, fraction = t)

        assertThat(newSpanStyle.fontStyle).isEqualTo(fontStyle2)
    }

    @Test
    fun `lerp fontSynthesis with a and b are not Null and t is smaller than half`() {
        val fontSynthesis1 = FontSynthesis.Style
        val fontSynthesis2 = FontSynthesis.Weight

        val t = 0.3f
        // attributes other than fontSynthesis are required for lerp not to throw an exception
        val style1 = SpanStyle(fontSynthesis = fontSynthesis1)
        val style2 = SpanStyle(fontSynthesis = fontSynthesis2)

        val newSpanStyle = lerp(start = style1, stop = style2, fraction = t)

        assertThat(newSpanStyle.fontSynthesis).isEqualTo(fontSynthesis1)
    }

    @Test
    fun `lerp fontSynthesis with a and b are not Null and t is larger than half`() {
        val fontSynthesis1 = FontSynthesis.Style
        val fontSynthesis2 = FontSynthesis.Weight

        val t = 0.8f
        // attributes other than fontSynthesis are required for lerp not to throw an exception
        val style1 = SpanStyle(fontSynthesis = fontSynthesis1)
        val style2 = SpanStyle(fontSynthesis = fontSynthesis2)

        val newSpanStyle = lerp(start = style1, stop = style2, fraction = t)

        assertThat(newSpanStyle.fontSynthesis).isEqualTo(fontSynthesis2)
    }

    @Test
    fun `lerp fontFeatureSettings with a and b are not Null and t is smaller than half`() {
        val fontFeatureSettings1 = "\"kern\" 0"
        val fontFeatureSettings2 = "\"kern\" 1"

        val t = 0.3f
        // attributes other than fontSynthesis are required for lerp not to throw an exception
        val style1 = SpanStyle(fontFeatureSettings = fontFeatureSettings1)
        val style2 = SpanStyle(fontFeatureSettings = fontFeatureSettings2)

        val newSpanStyle = lerp(start = style1, stop = style2, fraction = t)

        assertThat(newSpanStyle.fontFeatureSettings).isEqualTo(fontFeatureSettings1)
    }

    @Test
    fun `lerp fontFeatureSettings with a and b are not Null and t is larger than half`() {
        val fontFeatureSettings1 = "\"kern\" 0"
        val fontFeatureSettings2 = "\"kern\" 1"

        val t = 0.8f
        // attributes other than fontSynthesis are required for lerp not to throw an exception
        val style1 = SpanStyle(fontFeatureSettings = fontFeatureSettings1)
        val style2 = SpanStyle(fontFeatureSettings = fontFeatureSettings2)

        val newSpanStyle = lerp(start = style1, stop = style2, fraction = t)

        assertThat(newSpanStyle.fontFeatureSettings).isEqualTo(fontFeatureSettings2)
    }

    @Test
    fun `lerp baselineShift with a and b are not Null`() {
        val baselineShift1 = BaselineShift(1.0f)
        val baselineShift2 = BaselineShift(2.0f)
        val t = 0.3f
        val style1 = SpanStyle(baselineShift = baselineShift1)
        val style2 = SpanStyle(baselineShift = baselineShift2)

        val newSpanStyle = lerp(start = style1, stop = style2, fraction = t)

        assertThat(newSpanStyle.baselineShift)
            .isEqualTo(lerp(baselineShift1, baselineShift2, t))
    }

    @Test
    fun `lerp textGeometricTransform with a and b are not Null`() {
        val textTransform1 = TextGeometricTransform(scaleX = 1.5f, skewX = 0.1f)
        val textTransform2 = TextGeometricTransform(scaleX = 1.0f, skewX = 0.3f)
        val t = 0.3f
        val style1 = SpanStyle(textGeometricTransform = textTransform1)
        val style2 = SpanStyle(textGeometricTransform = textTransform2)

        val newSpanStyle = lerp(start = style1, stop = style2, fraction = t)

        assertThat(newSpanStyle.textGeometricTransform)
            .isEqualTo(lerp(textTransform1, textTransform2, t))
    }

    @Test
    fun `lerp locale with a and b are not Null and t is smaller than half`() {
        val localeList1 = LocaleList("en-US")
        val localeList2 = LocaleList("ja-JP")
        val t = 0.3f
        val style1 = SpanStyle(localeList = localeList1)
        val style2 = SpanStyle(localeList = localeList2)

        val newSpanStyle = lerp(start = style1, stop = style2, fraction = t)

        assertThat(newSpanStyle.localeList).isEqualTo(localeList1)
    }

    @Test
    fun `lerp locale with a and b are not Null and t is larger than half`() {
        val localeList1 = LocaleList("en-US")
        val localeList2 = LocaleList("ja-JP")
        val t = 0.8f
        val style1 = SpanStyle(localeList = localeList1)
        val style2 = SpanStyle(localeList = localeList2)

        val newSpanStyle = lerp(start = style1, stop = style2, fraction = t)

        assertThat(newSpanStyle.localeList).isEqualTo(localeList2)
    }

    @Test
    fun `lerp background with a and b are Null and t is smaller than half`() {
        val style1 = SpanStyle(background = Color.Unspecified)
        val style2 = SpanStyle(background = Color.Unspecified)

        val newSpanStyle = lerp(start = style1, stop = style2, fraction = 0.1f)

        assertThat(newSpanStyle.background).isEqualTo(Color.Unspecified)
    }

    @Test
    fun `lerp background with a is Null and b is not Null`() {
        val t = 0.1f
        val color1 = Color.Unspecified
        val style1 = SpanStyle(background = color1)
        val color2 = Color.Red
        val style2 = SpanStyle(background = color2)

        val newSpanStyle = lerp(start = style1, stop = style2, fraction = t)

        assertThat(newSpanStyle.background).isEqualTo(lerp(color1, color2, t))
    }

    @Test
    fun `lerp background with a is Not Null and b is Null`() {
        val t = 0.1f
        val color1 = Color.Red
        val style1 = SpanStyle(background = color1)
        val style2 = SpanStyle(background = Color.Unspecified)

        val newSpanStyle = lerp(start = style1, stop = style2, fraction = t)

        assertThat(newSpanStyle.background).isEqualTo(lerp(color1, Color.Unspecified, t))
    }

    @Test
    fun `lerp background with a and b are not Null and t is smaller than half`() {
        val color1 = Color.Red
        val color2 = Color.Green
        val t = 0.2f
        val style1 = SpanStyle(background = color1)
        val style2 = SpanStyle(background = color2)

        val newSpanStyle = lerp(start = style1, stop = style2, fraction = t)

        assertThat(newSpanStyle.background).isEqualTo(lerp(color1, color2, t))
    }

    @Test
    fun `lerp background with a and b are not Null and t is larger than half`() {
        val color1 = Color.Red
        val color2 = Color.Green
        val t = 0.8f
        val style1 = SpanStyle(background = color1)
        val style2 = SpanStyle(background = color2)

        val newSpanStyle = lerp(start = style1, stop = style2, fraction = t)

        assertThat(newSpanStyle.background).isEqualTo(lerp(color1, color2, t))
    }

    @Test
    fun `lerp textDecoration with a and b are not Null and t is smaller than half`() {
        val decoration1 = TextDecoration.LineThrough
        val decoration2 = TextDecoration.Underline
        val t = 0.2f
        val style1 = SpanStyle(textDecoration = decoration1)
        val style2 = SpanStyle(textDecoration = decoration2)

        val newSpanStyle = lerp(start = style1, stop = style2, fraction = t)

        assertThat(newSpanStyle.textDecoration).isEqualTo(decoration1)
    }

    @Test
    fun `lerp textDecoration with a and b are not Null and t is larger than half`() {
        val decoration1 = TextDecoration.LineThrough
        val decoration2 = TextDecoration.Underline
        val t = 0.8f
        val style1 = SpanStyle(textDecoration = decoration1)
        val style2 = SpanStyle(textDecoration = decoration2)

        val newSpanStyle = lerp(start = style1, stop = style2, fraction = t)

        assertThat(newSpanStyle.textDecoration).isEqualTo(decoration2)
    }

    @Test
    fun `lerp with null platformStyles have null platformStyle`() {
        val style = SpanStyle(platformStyle = null)
        val otherStyle = SpanStyle(platformStyle = null)

        val lerpedStyle = lerp(start = style, stop = otherStyle, fraction = 0.5f)

        assertThat(lerpedStyle.platformStyle).isNull()
    }

    @Test
    fun `lerp brush with a specified, b specified and t is smaller than half`() {
        val brush = Brush.linearGradient(listOf(Color.Blue, Color.Red))
        val style1 = SpanStyle(brush = brush)
        val style2 = SpanStyle(color = Color.Red)

        val newStyle = lerp(start = style1, stop = style2, fraction = 0.4f)

        assertThat(newStyle.brush).isEqualTo(brush)
        assertThat(newStyle.color).isEqualTo(Color.Unspecified)
    }

    @Test
    fun `lerp brush with a specified, b specified and t is larger than half`() {
        val brush = Brush.linearGradient(listOf(Color.Blue, Color.Red))
        val style1 = SpanStyle(brush = brush)
        val style2 = SpanStyle(color = Color.Red)

        val newStyle = lerp(start = style1, stop = style2, fraction = 0.6f)

        assertThat(newStyle.brush).isEqualTo(null)
        assertThat(newStyle.color).isEqualTo(Color.Red)
    }

    @Test
    fun `lerp brush with a specified, b not specified and t is larger than half`() {
        val brush = Brush.linearGradient(listOf(Color.Blue, Color.Red))
        val style1 = SpanStyle(brush = brush)
        val style2 = SpanStyle()

        val newStyle = lerp(start = style1, stop = style2, fraction = 0.6f)

        assertThat(newStyle.brush).isNull()
        assertThat(newStyle.color).isEqualTo(Color.Unspecified)
    }
}
